cmake_minimum_required(VERSION 4.0.3...4.2.0)

cmake_policy(SET CMP0167 OLD)

set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS TRUE)

if (CMAKE_VERSION GREATER_EQUAL 4.0.3 AND CMAKE_VERSION LESS_EQUAL 4.2.0)
    set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
endif ()

# Auto-detect and set vcpkg toolchain if not already set
if (NOT CMAKE_TOOLCHAIN_FILE)
    # First try environment variable CMAKE_TOOLCHAIN_FILE (for container builds)
    if (DEFINED ENV{CMAKE_TOOLCHAIN_FILE})
        set(CMAKE_TOOLCHAIN_FILE "$ENV{CMAKE_TOOLCHAIN_FILE}"
                CACHE STRING "Vcpkg toolchain file")
        message(STATUS "Using vcpkg toolchain from environment: ${CMAKE_TOOLCHAIN_FILE}")
        # Fallback to VCPKG_ROOT for backward compatibility
    elseif (DEFINED ENV{VCPKG_ROOT})
        set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
                CACHE STRING "Vcpkg toolchain file")
        message(STATUS "Using vcpkg toolchain from VCPKG_ROOT: ${CMAKE_TOOLCHAIN_FILE}")
    endif ()
endif ()

project(infinity VERSION 0.6.11)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)
set(CMAKE_CXX_MODULE_STD ON)

if (NOT CMAKE_GENERATOR STREQUAL "Ninja")
    message(FATAL_ERROR "This project requires the Ninja generator. Refers to https://cmake.org/cmake/help/latest/manual/cmake-cxxmodules.7.html#generator-support")
endif ()

# find_program(CCACHE_FOUND ccache)

# if(CCACHE_FOUND)
#     set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
#     set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) # Less useful to do it for linking, see edit2
#     message("Find ccache")
# else()
#     message("Can not find ccache")
# endif()

message(STATUS "CXX: ${CMAKE_CXX_COMPILER}")
execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version OUTPUT_VARIABLE clang_full_version_string)
string(REGEX REPLACE ".*clang version ([0-9]+\\.[0-9]+).*" "\\1" CLANG_VERSION_STRING ${clang_full_version_string})
if (CLANG_VERSION_STRING VERSION_GREATER_EQUAL 20)
    # Print CMake version and project name
    message(STATUS "Building ${PROJECT_NAME} with CMake version: ${CMAKE_VERSION} On CLANG-${CLANG_VERSION_STRING}")

    # add_compile_options(-ftime-trace)
    #    add_compile_options(-fmodule-header)
    add_link_options(-fuse-ld=mold)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-unused-private-field")

    add_link_options(-L/usr/local/lib)
    set(CMAKE_FIND_PACKAGE_SORT_ORDER NATURAL)
    set(CMAKE_FIND_PACKAGE_SORT_DIRECTION DEC)

else ()

    message(FATAL_ERROR "Please use clang version 20.0 and above, current version: ${CLANG_VERSION_STRING} ${CMAKE_CXX_COMPILER}")

endif ()

if (CMAKE_SYSTEM_PROCESSOR MATCHES "^arm64.*")
    set(ARM64 TRUE)
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^aarch64.*")
    set(ARM64 TRUE)
elseif (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64)$")
    set(X86_64 TRUE)
endif ()

#strength, for image maintainer
set(GCC_SEARCH_ROOTS
        /usr
        /usr/local
)

#if you encounter a problem, try commenting out the code below.
set(GCC15_SUFFIXES
        #        lib
        lib/gcc/15
        lib/gcc/x86_64-linux-gnu/15
        lib/gcc/x86_64-pc-linux-gnu/15.1.0
        lib64/gcc/x86_64-linux-gnu/15
        lib64/gcc/15
        lib64/gcc/x86_64-pc-linux-gnu/15.1.0
)

find_library(STDCXX15_STATIC
        NAMES libstdc++.a
        PATHS ${GCC_SEARCH_ROOTS}
        PATH_SUFFIXES ${GCC15_SUFFIXES}
        REQUIRED
        NO_DEFAULT_PATH
)

find_library(STDCXX15EXP_STATIC
        NAMES libstdc++exp.a
        PATHS ${GCC_SEARCH_ROOTS}
        PATH_SUFFIXES ${GCC15_SUFFIXES}
        REQUIRED
        NO_DEFAULT_PATH
)

get_filename_component(GCC15_LIB_DIR ${STDCXX15_STATIC} DIRECTORY)

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${GCC15_LIB_DIR} -static-libstdc++ -static-libgcc")
########

find_package(absl CONFIG REQUIRED)
find_package(Arrow CONFIG REQUIRED)
find_package(Boost REQUIRED COMPONENTS asio)
find_package(CLI11 CONFIG REQUIRED)
#find_package(CURL REQUIRED) # wait for s3
#darts-clone # wait for fix
#eigen3 fuck this package
#find_package(fmt REQUIRED) # wait for fmt:11.2.0
find_package(GTest CONFIG REQUIRED)
find_package(Libevent CONFIG REQUIRED)
find_package(magic_enum CONFIG REQUIRED)
#find_package(miniocpp CONFIG REQUIRED) # wait for s3
#nanobind   don't need it
find_package(nlohmann_json CONFIG REQUIRED)
find_package(oatpp CONFIG REQUIRED)
#if (X86_64)
#    find_package(opencc CONFIG REQUIRED) # wait for fix
#endif ()
find_package(OpenSSL REQUIRED)
find_package(pcre2 CONFIG REQUIRED)
find_package(pugixml CONFIG REQUIRED)
find_package(Parquet CONFIG REQUIRED)
find_path(PARALLEL_HASHMAP_INCLUDE_DIRS "parallel_hashmap/btree.h")
find_package(re2 CONFIG REQUIRED)
find_package(roaring CONFIG REQUIRED)
find_package(RocksDB CONFIG REQUIRED)
find_package(simdjson CONFIG REQUIRED)
#find_package(spdlog REQUIRED) # wait for fmt:11.2.0
if (ARM64)
    add_library(sse2neon INTERFACE)
    # need this after highway and before simdcomp
    find_path(SSE2NEON_INCLUDE_DIRS "sse2neon/sse2neon.h")
    target_include_directories(sse2neon SYSTEM ${SSE2NEON_INCLUDE_DIRS})
endif ()
find_package(tomlplusplus CONFIG REQUIRED)
#find_package(turbobase64 CONFIG REQUIRED)
find_package(Thrift CONFIG REQUIRED)
#find_package(unofficial-curlpp CONFIG REQUIRED) # for s3
find_package(unofficial-inih CONFIG REQUIRED)
find_path(VIT_VIT_CTPL_INCLUDE_DIRS "ctpl_stl.h")

set(VCPKG_PROJECT_ROOT "${CMAKE_CURRENT_BINARY_DIR}/vcpkg_installed")
file(GLOB VCPKG_INCLUDE_DIRS
        "${VCPKG_PROJECT_ROOT}/*/include"
)
message(STATUS "Found vcpkg include directories: ${VCPKG_INCLUDE_DIRS}")
include_directories(${VCPKG_INCLUDE_DIRS})

# vcpkg oatpp bug
file(GLOB OATPP_INCLUDE_DIRS
        "${VCPKG_PROJECT_ROOT}/*/include/oatpp-1.3.0/oatpp"
)
message(STATUS "Found oatpp include directories: ${OATPP_INCLUDE_DIRS}")
include_directories(${OATPP_INCLUDE_DIRS})
message(STATUS "Using vcpkg toolchain - automatic package discovery enabled")


# https://youtrack.jetbrains.com/issue/CPP-39632/import-std-CLion-cant-resolve-module-std-in-case-of-clang
add_library(unused_std_target STATIC)

# just tmp solution
set(LIBSTDCXX_MODULE_FILE_PATH_SUFFIX
        include/c++/15/bits
        include/c++/15.2.0/bits
)

find_file(LIBSTDCXX_MODULE_FILE
        NAMES std.cc
        PATHS ${GCC_SEARCH_ROOTS}
        PATH_SUFFIXES ${LIBSTDCXX_MODULE_FILE_PATH_SUFFIX}
        REQUIRED
)

get_filename_component(LIBSTDCXX_MODULE_FILE_DIR ${LIBSTDCXX_MODULE_FILE} DIRECTORY)

target_sources(unused_std_target
        PRIVATE
        FILE_SET CXX_MODULES
        BASE_DIRS ${LIBSTDCXX_MODULE_FILE_DIR}
        FILES ${LIBSTDCXX_MODULE_FILE_DIR}/std.cc ${LIBSTDCXX_MODULE_FILE_DIR}/std.compat.cc
)


# Get current system time and print the build time
execute_process(COMMAND "date" +"%Y-%m-%d %H:%M.%S" OUTPUT_VARIABLE CURRENT_SYS_TIME)
string(REGEX REPLACE "\n" "" CURRENT_SYS_TIME ${CURRENT_SYS_TIME})
message(STATUS "Build time = ${CURRENT_SYS_TIME}")

# Get git information. WARNING: For functions which invoke execute_process, the variable populated by execute_process is visiable only inside the function!
find_package(Git)
if (NOT Git_FOUND)
    message(FATAL_ERROR "Git not found.")
endif ()
execute_process(
        COMMAND "${GIT_EXECUTABLE}" symbolic-ref --short HEAD
        OUTPUT_VARIABLE GIT_BRANCH_NAME
        OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
        COMMAND "${GIT_EXECUTABLE}" describe --tags --abbrev=0
        OUTPUT_VARIABLE GIT_TAG
        OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
        COMMAND "${GIT_EXECUTABLE}" rev-list --count "${GIT_TAG}..HEAD"
        OUTPUT_VARIABLE COMMIT_COUNT
        OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
        COMMAND "${GIT_EXECUTABLE}" rev-parse --short HEAD
        OUTPUT_VARIABLE HEAD_COMMIT_ID
        OUTPUT_STRIP_TRAILING_WHITESPACE)

set(GIT_COMMIT_ID "${GIT_TAG}-${COMMIT_COUNT}-${HEAD_COMMIT_ID}")
if ("${GIT_BRANCH_NAME}" STREQUAL "")
    message(WARNING "Branch name not found.")
else ()
    message(STATUS "Branch name = ${GIT_BRANCH_NAME}")
endif ()
if ("${HEAD_COMMIT_ID}" STREQUAL "")
    message(WARNING "Commit id not found.")
else ()
    message(STATUS "Commit-id = ${GIT_COMMIT_ID}")
endif ()

# attach additional cmake modules
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")

# You can disable jemalloc by passing the `-DENABLE_JEMALLOC=OFF` option to CMake.
option(ENABLE_JEMALLOC "Enable jemalloc support" ON)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
    message(STATUS "Disable jemalloc in Debug mode")
    set(ENABLE_JEMALLOC OFF)
endif ()

if (ENABLE_JEMALLOC)
    find_package(jemalloc REQUIRED)
    set(JEMALLOC_STATIC_LIB "jemalloc_pic.a")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_JEMALLOC_PROF")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DENABLE_JEMALLOC_PROF")
endif ()

if (NOT CMAKE_BUILD_TYPE)
    if (EXISTS "${CMAKE_SOURCE_DIR}/.git")
        set(default_build_type "RelWithDebInfo")
    else ()
        set(default_build_type "Debug")
    endif ()

    set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
            "Default BUILD_TYPE is ${default_build_type}" FORCE)
endif ()

message(STATUS "Build type = ${CMAKE_BUILD_TYPE}")

option(ENABLE_SANITIZER_THREAD "Enable multi-thread sanitizer" OFF)

if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")

    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG")

elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")

    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O2 -g -DNDEBUG")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -g -DNDEBUG")

elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")

    set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O0 -g")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g")

    if ("${CODE_COVERAGE}" STREQUAL "ON")
        add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
        add_link_options(-fprofile-instr-generate -fcoverage-mapping)
    else ()
        set(CODE_COVERAGE "OFF")
    endif ()

    message(STATUS "Code Coverage = ${CODE_COVERAGE}")

    if (NOT ENABLE_JEMALLOC)
        if (NOT ENABLE_SANITIZER_THREAD)
            add_compile_options(-fsanitize=address -fsanitize=leak)
            add_link_options(-fsanitize=address -fsanitize=leak)
            message(STATUS "Enable Address Sanitizer in target: infinity")
        else ()
            add_compile_options(-fsanitize=thread)
            add_link_options(-fsanitize=thread)
            message(STATUS "Enable Thread Sanitizer in target: infinity")
        endif ()

        message(STATUS "Enable Sanitizer in target: infinity")

        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector -fno-var-tracking ")
        add_compile_options(-fsanitize-recover=all)
        add_link_options(-fsanitize-recover=all)

        add_compile_options("-fno-omit-frame-pointer")
        add_link_options("-fno-omit-frame-pointer")

        #    add_compile_options("-fsanitize=undefined")
        #    add_link_options("-fsanitize=undefined")

    else ()
        message(STATUS "Disable AddressSanitizer because jemalloc")
    endif ()

    set(CMAKE_DEBUG_POSTFIX "")

else ()
    message(FATAL_ERROR "Only support CMake build type: Debug, RelWithDebInfo, and Release")
endif ()

if (CLANG_VERSION_STRING VERSION_EQUAL 17)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wno-asm-operand-widths -Wno-unused-command-line-argument -Wno-deprecated-declarations -Wno-read-modules-implicitly -Wextra -Wno-unused-parameter -Wno-unused-private-field -pthread -fcolor-diagnostics")
else ()
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -Wno-asm-operand-widths -Wno-unused-command-line-argument -Wno-deprecated-declarations -Wextra -Wno-unused-parameter -Wno-unused-private-field -pthread -fcolor-diagnostics")
endif ()

MESSAGE(STATUS "C++ Compilation flags: " ${CMAKE_CXX_FLAGS})

if (APPLE)
    if (X86_64)
        execute_process(COMMAND sh -c "sysctl -a machdep.cpu.features | grep SSE4.2"
                RESULT_VARIABLE SUPPORT_SSE42
                OUTPUT_QUIET
                ERROR_QUIET)

        execute_process(COMMAND sh -c "sysctl -a machdep.cpu.features | grep AVX2"
                RESULT_VARIABLE SUPPORT_AVX2
                OUTPUT_QUIET
                ERROR_QUIET)

        execute_process(COMMAND sh -c "sysctl -a machdep.cpu.features | grep AVX512"
                RESULT_VARIABLE SUPPORT_AVX512
                OUTPUT_QUIET
                ERROR_QUIET)
    endif ()
else ()
    #Linux
    if (X86_64)
        execute_process(COMMAND grep -qw sse4_2 /proc/cpuinfo
                RESULT_VARIABLE SUPPORT_SSE42
                OUTPUT_QUIET
                ERROR_QUIET)

        execute_process(COMMAND grep -qw avx2 /proc/cpuinfo
                RESULT_VARIABLE SUPPORT_AVX2
                OUTPUT_QUIET
                ERROR_QUIET)

        execute_process(COMMAND grep -qw avx512f /proc/cpuinfo && grep -qw avx512dq /proc/cpuinfo && grep -qw avx512bw /proc/cpuinfo && grep -qw avx512vl /proc/cpuinfo
                RESULT_VARIABLE SUPPORT_AVX512
                OUTPUT_QUIET
                ERROR_QUIET)
    endif ()
endif ()

add_definitions(-DSIMDE_ENABLE_NATIVE_ALIASES)
if (X86_64)
    if (SUPPORT_AVX512 EQUAL 0)
        message(STATUS "Compiled by AVX512")
        add_definitions(-mavx512f -mavx512dq -mavx512bw -mavx512vl -mevex512)
    elseif (SUPPORT_AVX2 EQUAL 0)
        message(STATUS "Compiled by AVX2")
        add_definitions(-mavx2)
    elseif (SUPPORT_SSE42 EQUAL 0)
        message(STATUS "Compiled by SSE4_2")
        add_definitions(-msse4.2)
    else ()
        message(FATAL_ERROR "This project requires following cpu flag: sse4_2.")
    endif ()
    add_definitions(-mfma -mf16c -mpopcnt -mbmi2)
else ()
    add_definitions(-march=native)
endif ()

execute_process(
        COMMAND bash -c "zgrep CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y /proc/config.gz 2>/dev/null; grep CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y /boot/config-$(uname -r) 2>/dev/null"
        OUTPUT_VARIABLE HAVE_EFFICIENT_UNALIGNED_ACCESS
        OUTPUT_STRIP_TRAILING_WHITESPACE)
if ("${HAVE_EFFICIENT_UNALIGNED_ACCESS}" STREQUAL "CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y")
    message(STATUS "Found CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y")
    add_definitions(-DHAVE_EFFICIENT_UNALIGNED_ACCESS)
endif ()

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    ADD_DEFINITIONS(-D INFINITY_DEBUG)
endif ()

if (NOT INDEX_BUILD_METHOD STREQUAL "Variant")
    message(STATUS "Add Environment Variable INDEX_HANDLER")
    ADD_DEFINITIONS(-D INDEX_HANDLER)
else ()
    message(STATUS "NOT USING INDEX_HANDLER")
endif ()

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
find_package(Python COMPONENTS Interpreter Development REQUIRED)
#find_package(Python 3.8
#        REQUIRED COMPONENTS Interpreter Development.Module
#        OPTIONAL_COMPONENTS Development.SABIModule)

if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif ()

#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/third_party/nanobind)

add_subdirectory(src)
add_subdirectory(third_party EXCLUDE_FROM_ALL)

# set parameters for unit test coverage
# TODO: issue error "cannot merge previous GCDA file" when turn following switch.
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")

# Compile the unit test

# Compile benchmark
add_subdirectory(benchmark)
add_subdirectory(client)

# quit early for scikit-build-core
# does not need CPack settings
# does not need to install the infinity target, config files and resources
if (SKBUILD)
    return()
endif ()

# CPack settings
set(CPACK_PACKAGE_NAME "infinity")
# dpkg requires version to be starting with number
if (DEFINED CPACK_PACKAGE_VERSION)
    string(REGEX REPLACE "^[^0-9]+" "" CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}")
    string(REPLACE "-" "." CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}")
endif ()
if (NOT DEFINED CPACK_PACKAGE_VERSION OR CPACK_PACKAGE_VERSION STREQUAL "")
    set(CPACK_PACKAGE_VERSION "${GIT_COMMIT_ID}")
endif ()
set(CPACK_PACKAGE_RELEASE 1)
set(CPACK_PACKAGE_CONTACT "Zhichang Yu <yuzhichang@gmail.com>")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The AI-native database built for LLM applications, offering incredibly fast vector and full-text search.")
set(CPACK_PACKAGE_VENDOR "infiniflow")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_HOST_SYSTEM_PROCESSOR}")

# https://cmake.org/cmake/help/latest/command/install.html
# Install target
# WANNING: If an absolute path is given, cpack will install the specific files on the host system (requires root permission) and then included in the package.
# If an relative path (interpreted relative to the value of the CMAKE_INSTALL_PREFIX variable) is given, cpack include specific files in the package without actually installing them on the host system.
# CMAKE_INSTALL_PREFIX defaults to "/usr/local"
set(CMAKE_INSTALL_PREFIX /usr)
install(TARGETS infinity DESTINATION bin)
install(FILES conf/infinity.service DESTINATION lib/systemd/system)
install(FILES conf/infinity_conf.toml DESTINATION etc)

# https://cmake.org/cmake/help/latest/cpack_gen/rpm.html
# Specify the post-install script for RPM
# CPackRPM needs the absolute path of the file as CPack does not know that script is relative to source tree.
set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/conf/postinst")

# https://cmake.org/cmake/help/latest/cpack_gen/deb.html
# Add custom script to the control.tar.gz. Typical usage is for conffiles, postinst, postrm, prerm.
# Note: DEB requires the file name be one of postinst, postrm, prerm and the "+x" permission, while rpm doesn't require that.
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/conf/postinst")

# Specify the package generators
set(CPACK_GENERATOR "RPM;DEB;TGZ")

# Enable CPack debug output
set(CPACK_PACKAGE_DEBUG True)

# https://cmake.org/cmake/help/latest/variable/CPACK_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION.html
set(CPACK_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION "ON")
include(CPack)
